package arc;

import com.arc.message.Authentication;
import com.arc.level.Snapshot;
import com.arc.level.NetworkedNode;
import com.arc.message.ChangeRequest;
import com.arc.level.ServerLevelState;
import com.arc.level.Snapshot;
import com.arc.message.InitializeScene;
import com.arc.message.SerializableVector;
import com.arc.message.TickMessage;
import com.arc.state.AbstractState;
import com.arc.state.MessageState;
import com.jme3.app.SimpleApplication;
import com.jme3.math.Transform;
import com.jme3.math.Vector3f;
import com.jme3.network.ConnectionListener;
import com.jme3.network.Filter;
import com.jme3.network.HostedConnection;
import com.jme3.network.Message;
import com.jme3.network.MessageListener;
import com.jme3.network.Network;
import com.jme3.network.Server;
import com.jme3.network.serializing.Serializer;
import com.jme3.renderer.RenderManager;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.system.JmeContext;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.logging.Level;
import java.util.logging.Logger;

public class ArcServer extends ArcBase implements ConnectionListener, MessageListener<HostedConnection>{    
    static final String version = "0.0.1";
    @Deprecated
    static final long tickrate = 15L;
    static final float tickRate = .015f;
    static int         snapshot = 0;
    static float       timer   = 0;
    public static final int   snapshotMemory = 3; 
    private long       serverTime = 0;
    private long       startTime  = 0;
    private Server              myServer;
    volatile private ServerLevelState    levelState;
    private ScheduledThreadPoolExecutor serverLogic = new ScheduledThreadPoolExecutor(4);
    private Future nextTick = null; 
    private float tickTime = 0;
    private ConcurrentLinkedQueue<HostedConnection>         initializeQueue = new ConcurrentLinkedQueue<HostedConnection>(); //A collection of clients who are either still initializing or authenticating
    private ConcurrentLinkedQueue<TickMessage>  tickQueue = new ConcurrentLinkedQueue<TickMessage>();
    
    public ArcServer()
    {
        Serializer.registerClass(Authentication.class);        
        Serializer.registerClass(MessageState.class);       
        Serializer.registerClass(AbstractState.class);
        Serializer.registerClass(TickMessage.class);
        Serializer.registerClass(Snapshot.class);  
        Serializer.registerClass(Snapshot.class); 
        Serializer.registerClass(ChangeRequest.class);  
        Serializer.registerClass(SerializableVector.class);      
    }
    
    @Override
    public void simpleInitApp() {
        startTime = System.currentTimeMillis();
        
        levelState = new ServerLevelState();
        getStateManager().attach(levelState);
        
        try {
            myServer = Network.createServer(6143);
            myServer.addConnectionListener(this);
            myServer.addMessageListener(this);
            myServer.start();
            
            System.out.println("\n*****************Server started************************\n");
        } catch (IOException ex) {
            Logger.getLogger(ArcServer.class.getName()).log(Level.SEVERE, null, ex);
        }
        
    }
    
    @Override
    public void destroy()
    {
        System.out.println("[Server]\tDestroy");
    }
    
    @Override
    public void stop()
    {
        super.stop();
        serverLogic.shutdown();
    }
    
    @Override
    public void simpleUpdate(float tpf) {
        serverLogic.execute(new Runnable()
        {
            public void run(){
                //levelState.processChanges();
            }
        });
        
        if(!initializeQueue.isEmpty()){
            serverLogic.execute(new Runnable()
            {
                public void run() { 
                    NetworkedNode tree = levelState.generateNetworkedSceneGraph(levelState.getRoot(), null);
                    
                    
                    while(!initializeQueue.isEmpty())
                    {
                        HostedConnection player = initializeQueue.poll();
                        
                        TickMessage tick = new TickMessage();
                        Snapshot snap = new Snapshot();
                        snap.setRoot(tree);
                        snap.setSentTime(System.currentTimeMillis());
                        tick.setSnapshot(snap);   
                        tick.setReliable(true);    
                        player.send(tick);
                        player.setAttribute("initialized", true);
                    }
                }               
            });            
        }
        
        try
        {                
            //Attempt to create client ticks
            TickLogic(tpf);
        }        
        catch(Exception e)
        {
            e.printStackTrace();
        }
    }
    
    public void TickLogic(float tpf) throws InterruptedException, ExecutionException
    {
        if(nextTick == null)
        {
            if(tickTime > tickRate)
            {
                nextTick = serverLogic.submit(new Callable<TickMessage>()
                {
                    public TickMessage call() throws Exception {
                        Snapshot currentSnapshot = null;
                        //currentSnapshot = levelState.generateSnapshot(snapshot);
                        
                        TickMessage tick = new TickMessage();
                        tick.setReliable(false);
                        tick.setSnapshot(currentSnapshot);

                        return tick;
                    }
                });

                tickTime = 0;
            }else{                    
                tickTime += tpf;
            }
        }else if(nextTick != null)
        {

            if(nextTick.isDone())
            {

                TickMessage tick = (TickMessage)nextTick.get();                  
                
                if(tick != null)
                {                    
                    snapshot+=1L;
                    for(HostedConnection player : myServer.getConnections())
                    {
                        if((Boolean)player.getAttribute("initialized") == true)
                        {                                     
                            tick.getSnapshot().setSentTime(System.currentTimeMillis());
                            player.send(tick);
                        }else{                   
                            if(!initializeQueue.contains(player))
                                initializeQueue.add(player);                          
                        }
                    }                    
                }else{
                    System.out.println("Coudln't send tick, tick is null");
                }

                nextTick = null;
            }else if(nextTick.isCancelled()){                    
                nextTick = null;
            }           
        }
    }
    
    public boolean submitChangeRequest(ChangeRequest request)
    {
        return true;
    }
    
    public long getServerTime()
    {        
       serverTime = System.currentTimeMillis() - startTime;
       return serverTime;
    }

    @Override
    public void simpleRender(RenderManager rm) {
        //TODO: add render code
    }

    public void connectionAdded(Server server, HostedConnection conn) {
        System.out.println("[Server] Client " + conn.getId() + " connected.");
        conn.setAttribute("authenticated", false);        
        conn.setAttribute("initialized", false);
    }

    public void connectionRemoved(Server server, HostedConnection conn) {        
        System.out.println("[Server] Client " + conn.getId() + " disconnected");
    }

    public void messageReceived(HostedConnection source, Message msg) {
        if(msg instanceof Authentication)
        {
            Authentication shake = (Authentication)msg;
            
            if(!shake.authenticated)
            {
                if(!shake.getClientName().equals(myServer.getGameName()) || shake.getClientVersion() != myServer.getVersion())
                {
                    shake.authenticated = false;
                }else{
                    shake.authenticated = true;
                }                              

                source.setAttribute("authenticated", shake.authenticated);
                source.setAttribute("initialized", false);              
                
                if(shake.authenticated)
                {
                    initializeQueue.add(source);
                    source.send(shake);
                    
                }else{
                    source.close("Client version out of date");
                }
            }         
        }
    }  
    
}
